package samples;

import its.opt.structures.BOAParams;
import its.util.RandomFactory;

import java.util.ArrayList;
import java.util.List;

import samples.objectivefunctions.DeceptiveOneMax;
import engine.BoaAlgorithm;
import engine.BoaPopulation;
import engine.CachedObjectiveFunction;
import engine.PopulationEvaluator;
import engine.SingleThreadedEvaluator;
import engine.exitcriteria.MaxIterations;
import engine.individuals.BinaryVectorIndividual;
import engine.operators.BOAOffspringGeneratorOperator;
import engine.operators.BoaReplaceWorstOperator;
import engine.operators.TournamentSelectionOperator;
import engine.utils.ListUtils;

/**
 * Bayesian Optimization Algorithm
 * 
 * @author Rafal Hladyszowski
 */
public class BOA {

	/** Number of iterations. */
	private static final int MAX_ITER = 20;

//	/** Pure chance probability. */
//	private static final double DEFAULT_PROBABILITY = 0.5;

	/** Size of vector to be optimized. */
	private static final int VECTOR_SIZE = 60;

	/** Size of the population. */
	private static final int POPULATION_SIZE = 600;

	/**
	 * Entry point to the program.
	 * 
	 * @param args
	 *          ignored.
	 */
	public static void main(String[] args) {

		BOAParams boaParams = prepareParams();
		BoaPopulation<BinaryVectorIndividual> population = new BoaPopulation<BinaryVectorIndividual>(
				generateListOfRandomBinaryIndividuals(boaParams), boaParams.n);

		System.out.println(population.toString());

		BoaAlgorithm<BinaryVectorIndividual> alg = new BoaAlgorithm<BinaryVectorIndividual>(
				population, boaParams.n);
		CachedObjectiveFunction<BinaryVectorIndividual> objectiveFunctionWrapper = new CachedObjectiveFunction<BinaryVectorIndividual>(
				new DeceptiveOneMax());
		alg.addExitPoint(new MaxIterations<BinaryVectorIndividual>(
				boaParams.maxGenerations));
		alg.addEvaluationPoint(buildObjectiveFunctions(objectiveFunctionWrapper));
		// Operator selekcji ?? jednak nie
		// alg.addOperator(new BestHalfSelection<Program>(new
		// ProgramTrafficAmount()));
		alg.addOperator(new TournamentSelectionOperator<BinaryVectorIndividual>(
				objectiveFunctionWrapper, boaParams));

		// Operator Learning BOA (1) Learning the structure (2) Learning the
		// conditional probabilities
		// Operator sampling population
		// create offspring - generates offspring in the BOA (constructs the network
		// for the
		// selected strings, and then uses it to generate new strings)
		alg.addOperator(new BOAOffspringGeneratorOperator<BinaryVectorIndividual>(
				boaParams));

		// Operator replacement
		// replace the worst of the population with offspring
		alg.addOperator(new BoaReplaceWorstOperator<BinaryVectorIndividual>(
				objectiveFunctionWrapper));

		// alg.addOperator(new UniformCrossover(new SecureRandom()));
		// alg.addOperator(new
		// UniformProbabilityNegationMutation(MUTATION_PROBABILITY,
		// new SecureRandom()));
		alg.run();

		System.out.println(alg.getPopulation().toString());
	}

	private static BOAParams prepareParams() {

		BOAParams params = new BOAParams();

		params.N = POPULATION_SIZE;
		params.n = VECTOR_SIZE;
		params.percentOffspring = 50;
		params.tournamentSize = 4;
		params.randSeed = 123456;
		params.allowMerge = true;
		params.maxGenerations = MAX_ITER;
		params.epsilon = 0.01f;
		params.maxIncoming = 20;

		return params;
	}

	/**
	 * Creates a list of objective functions (one in this case) to be optimized.
	 * 
	 * @param objectiveFunctionWrapper
	 *          A function that the list is based on.
	 * @return List of objective functions.
	 */
	@SuppressWarnings("unchecked")
	private static PopulationEvaluator<BinaryVectorIndividual> buildObjectiveFunctions(
			CachedObjectiveFunction<BinaryVectorIndividual> objectiveFunctionWrapper) {
		return new SingleThreadedEvaluator<BinaryVectorIndividual>(ListUtils
				.buildList(objectiveFunctionWrapper));
	}

	/**
	 * Generates initial population with random binary individuals.
	 * 
	 * @param boaParams
	 * 
	 * @return List of binary individuals that will form the basis for first
	 *         iteration of the algorithm.
	 */
	private static List<BinaryVectorIndividual> generateListOfRandomBinaryIndividuals(
			BOAParams boaParams) {
		// TODO(marcin|karol): move this functionality as a static factory method
		// in BinaryIndividual
		List<BinaryVectorIndividual> result = new ArrayList<BinaryVectorIndividual>();
		for (int i = 0; i < boaParams.N; i++) {
			boolean[] res = new boolean[boaParams.n];
			for (int j = 0; j < res.length; j++) {
				res[j] = RandomFactory.getNextBoolean();
			}
			result.add(new BinaryVectorIndividual(res));
		}
		return result;
	}
}
